import Data.List
data Board = Board [[Square]] Square
instance Show Board where
    show (Board board turn) = showBoard (transpose board) ++ "\n"++ (show turn)

data Square = Empty | Black | White deriving Eq
instance Show Square where
    show Empty = "[ ]"
    show Black = "[B]"
    show White = "[W]"

type Coord = (Int, Int)

(!!!) :: Board -> Coord -> Square
(Board board turn) !!! (x,y) = board!!x!!y

(+++) :: Coord -> Coord -> Coord
(x, y) +++ (dx,dy) = (x+dx, y+dy)

turn :: Board -> Square
turn (Board b t) = t

outOfBounds :: Board -> Coord ->  Bool
outOfBounds (Board b _) (x,y) = x<0||y<0||x>=l||y>=l
                            where l = length b

capturedPiecesDir :: Board -> Coord -> Coord  -> [Coord]
capturedPiecesDir b loc dir
    | outOfBounds b loc || outOfBounds b loc'   --clicked location and next square must be valid 
   || b!!!loc /= Empty                          --clicked location must be empty
   || b!!!loc' `elem` [Empty, turn b]  = []     --and the next square must not be empty, or our color. 
    | otherwise = case nextPiece loc' dir b of
                     Nothing -> []             
                     Just finish -> getCoordsBetween loc dir finish
    where loc' = loc+++dir

nextPiece loc dir b
    | outOfBounds b loc
   || piece == Empty     = Nothing
    | piece == (turn b) = Just loc
    | otherwise         = nextPiece (loc+++dir) dir b
     where piece = b!!!loc

getCoordsBetween (x, y) (dx, dy) (fx, fy) = zip [x,x+dx..fx] [y,y+dy..fy]
          
capturedPieces :: Board -> Coord -> [Coord] 
capturedPieces b loc =
           let dirs =  [(x, y) | x <- [-1,0,1], y <- [-1,0,1]]
           in concatMap (capturedPiecesDir b loc) dirs

--Functions for displaying the game board
printBoard :: [[Square]] -> IO()
printBoard board = do
    putStrLn $ showBoard $  board
    return()

showBoard :: [[Square]] -> String
showBoard [] = ""
showBoard (x:xs) = (printLine x) ++ "\n" ++(showBoard xs)

printLine :: [Square] -> String
printLine [] = ""
printLine (x:xs) = show x ++ " " ++ (printLine xs)


--Functions for starting the game
startBoard :: Integer -> Board
startBoard height = Board [[chooseSquare height x y | x <- [1..height]] | y<-[1..height]] Black

chooseSquare :: Integer -> Integer -> Integer -> Square
chooseSquare height x y
    | (height `div` 2) == x && (height `div` 2) == y = Black
    | (height `div` 2)+1 == x && (height `div` 2)+1 == y = Black
    | (height `div` 2) == x && (height `div` 2)+1 == y = White
    | (height `div` 2)+1 == x && (height `div` 2) == y = White
    | otherwise = Empty

--Functions for determining if a move is proper, or if a square should be flipped
--Fix from here down to use Board data type

isCapture :: Coord -> Int -> Int -> Board -> [(Int,Int)]
isCapture (x,y) dx dy (Board board turn)
  | (((x+dx) < 0) || ((y+dy) < 0)) =  []--below 0 checker
  | ((x+dx) >= (length board)) || ((y+dy)>=(length board)) = [] --out of bound checker
  | (Board board turn) !!! (x,y) /= Empty = []  --clicked location must be empty
  | (Board board turn) !!! ((x+dx),(y+dy)) == Empty = [] --moving in our dx,dy direction, the next square must not be empty.
  | (Board board turn) !!! ((x+dx),(y+dy)) == turn = [] --the next square must not be our color, either.
  | (traverseBoard (x,y) dx dy (Board board turn)) == Nothing = []
  | otherwise =
       let (Just coords) = (traverseBoard (x,y) dx dy (Board board turn))
       in (getCoordsBetween (x,y) (dx, dy) (coords))

--we know there is at least one piece to flip. We now search for a piece our color and return the coord of the first found one
traverseBoard :: Coord -> Int -> Int -> Board -> Maybe (Int,Int)
traverseBoard (x,y) dx dy (Board board turn)
  | minimum [x, y, x+dx, y+dy] < 0 = Nothing
  | maximum [x,y, x+dx, y+dy] >= (length board) + 1 = Nothing
  | ((board !! (x+dx)) !! (y+dy)) == Empty = Nothing
  | ((board !! (x+dx)) !! (y+dy)) /= turn = (traverseBoard ((x+dx),(y+dy)) dx dy (Board board turn))
  | otherwise = Just ((x+dx),(y+dy))


--This function takes an x and y (where they selected to move), a dx and dy, and a coord. We will return all coords between (x,y) and (fx, fy)
--getCoordsBetween :: Int -> Int -> Int -> Int -> (Int,Int) -> [(Int, Int)]
--getCoordsBetween x y dx dy (fx,fy) = if (fx == x && fy == y) then [] else (x,y):(getCoordsBetween (x+dx) (y+dy) dx dy (fx,fy))
 
  -- | ((board !! (x+dx)) !! (y+dy)) == turn = [] --moving in dx dy direction, squares must not be our color already
  -- | otherwise = isCapture' (x) (y) dx dy (Board board turn)

--isCapture' x y dx dy (Board board turn) = [(77,77)]


{-
isCapture :: Integer -> Integer -> Integer -> Integer -> Board -> [(Integer,Integer)]
isCapture x y dx dy (Board board turn)
  | (x >=(length board)-1) || (y>=(length board)-1) = []
  | ((board !! x) !! y) /= Empty = []
  | ((board !! (x+dx)) !! (y+dy)) == turn = []
  | otherwise = isCapture' (x) (y) dx dy board turn []

isCapture' x y dx dy board turn xs
  | (x >=(length board)-1) || (y>=(length board)-1)= []
  | ((board !! (x+dx)) !! (y+dy)) == turn = ((x,y):xs)
  | ((board !! (x+dx)) !! (y+dy)) == Empty = []
  | otherwise = isCapture' (x+dx) (y+dy) dx dy board turn (((x+dx),(y+dy)):xs)
-}

{-
isCapture x y dx dy board turn
  | (x >=(length board)-1) || (y>=(length board)-1) = []
  | ((board !! x) !! y) /= Empty = []
  | ((board !! (x+dx)) !! (y+dy)) == turn = []
  | otherwise = isCapture' (x) (y) dx dy board turn

isCapture' x y dx dy board turn
  | (x>=(length board)-1) || (y>=(length board)-1) = []
  | ((board !! (x+dx)) !! (y+dy)) == turn = [(x,y)]
  | ((board !! (x+dx)) !! (y+dy)) == Empty = []
  | otherwise = isCapture' (x+dx) (y+dy) dx dy board turn
-}

--Functions that are in the move-making process
toUpdate :: Board -> Coord -> [(Int,Int)]
toUpdate (Board board turn) (x,y) = 
           let moves = [-1,0,1]
           in concat [isCapture (x,y) dx dy (Board board turn) | dx <- moves, dy <- moves]
  --(isCapture xy 1 0 (Board board turn))++(isCapture xy (-1) 0 (Board board turn))++(isCapture xy 0 1 (Board board turn))++(isCapture xy 0 (-1)  (Board board turn))++(isCapture xy 1 1 (Board board turn))++(isCapture xy 1 (-1) (Board board turn))++(isCapture xy (-1) 1 (Board board turn))++(isCapture xy (-1) (-1) (Board board turn))



blackSquares (Board board _) = [[if (((board !! x) !! y) == Black) then (x,y) else (-1,-1) | x <- [0..(length board)-1]] | y<-[0..(length board)]]

whiteSquares (Board board _) = [[if (((board !! x) !! y) == White) then (x,y) else (-1,-1) | x <- [0..(length board)]] | y<-[0..(length board)]]

--updateBoard (Board board a) turn x y = updateBoardHelper (Board board a) turn (toUpdate (Board board a) x y)

--updateBoardHelper (Board board a) turn [] = (Board board turn)
--updateBoardHelper (Board board a) turn (l:lst) = updateBoardHelper (updateAt (Board board turn) (fst l) (snd l) turn) turn lst
--updateHelper (Board board turn) [] = board
--updateHelper (Board board turn) (x:xs) = updateHelper (updateAt board (fst x) (snd x) turn) turn xs
--updateBoard (Board board turn) x y = updateHelper board turn (toUpdate board turn x y)


updateAt :: Board -> Int -> Int -> Square -> Board
updateAt (Board board turn) x y sqr = (Board ([[if x == a && y == b then sqr else getSquareAt (Board board turn) a b | a <- [0.. (length board)-1]] | b<-[0.. (length board)-1]]) turn)

getSquareAt :: Board -> Int -> Int -> Square
getSquareAt (Board board _) x y = ((board !! y) !! x)

--empties board = [ x | x<-[0..length board -1], Empty `elem` board !! x]
   
--whoWon board =
--    if length (empties board) > 0 then Empty
--    else let pnts = score board
--         in
--           if fst pnts > snd pnts then Black
--           else White

--score board = scoreHelp board 0 0

--scoreHelp [] bpnts wpnts = (bpnts,wpnts)
--scoreHelp (x:xs) bpnts wpnts =
--    let (lstb,lstw) = ([y | y <- x, y == Black],[z | z <- x, z == White])
--    in scoreHelp xs (bpnts+(length lstb)) (wpnts+(length lstw))

getNumFlipped :: Coord -> Board -> [Int]
getNumFlipped (x,y) (Board board turn)
  | (x /= (length board-1)) = (length (toUpdate (Board board turn) (x,y))):(getNumFlipped ((x+1),y) (Board board turn))
  | (y /= (length board-1)) = (length (toUpdate (Board board turn) (x,y))):(getNumFlipped (0,(y+1)) (Board board turn))
  | otherwise = []

getBestIndex :: Int -> Coord -> [Int] -> Int -> Int -> Int -> Coord
getBestIndex len (x,y) [] m mx my = (mx,my)
getBestIndex len (x,y) (n:ns) m mx my
  | (x /= (len)) = if (n > m) then (getBestIndex len ((x+1),y) ns n x y) else (getBestIndex len ((x+1),y) ns m mx my)
  | (y /= (len)) = if (n > m) then (getBestIndex len (0,(y+1)) ns n x y) else (getBestIndex len (0,(y+1)) ns m mx my)

bestMove :: Board -> Coord
bestMove (Board board turn) = (getBestIndex ((length board)-1) (0,0) (getNumFlipped (0,0) (Board board turn)) 0 0 0)

makeMove :: Board -> Coord -> Board
makeMove (Board board turn) (x,y) = (makeMove' (Board board turn) (toUpdate (Board board turn) (x,y)))

makeMove' :: Board -> [(Int,Int)] -> Board
makeMove' (Board board turn) [] = if (turn == Black) then (Board board White) else (Board board Black)
makeMove' (Board board turn) ((x,y):xys) = (makeMove' (updateAt (Board board turn) y x turn) xys)

